/*
 * Copyright (C) 2010-2011 Graz University of Technology, Austria. All Rights reserved. 
 *
 * Contact: Simone Kopeinik <simone.kopeinik@tugraz.at>
 * 	   Graz University of Technology, Knowledge Management Institute, Brückenkopfgasse 1/6, 8020 Graz 	
 * 	   <http://www.kmi.tugraz.at/>
 * 
 * This software is part of the TARGET platform developed by the
 * TARGET EC-funded project www.reachyourtarget.org
 *
 * This software is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * The GNU General Public License is available at <http://www.gnu.org/licenses/>.
 *
 */
package at.tugraz.kmi.css.cbkst2.prereq.io;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import org.apache.commons.collections15.Factory;
import org.apache.commons.collections15.Transformer;

import at.tugraz.kmi.css.cbkst2.CbKSTCompetence;
import at.tugraz.kmi.css.cbkst2.CbKSTVertex;
import at.tugraz.kmi.css.cbkst2.ImmutablePrerequesiteOrderSet;
import at.tugraz.kmi.css.cbkst2.PERSISTENCE_TYPE;
import at.tugraz.kmi.css.cbkst2.PrerequesiteOrderSet;
import at.tugraz.kmi.css.cbkst2.PrerequesiteRelation;
import at.tugraz.kmi.css.cbkst2.StructureLoader;
import at.tugraz.kmi.css.cbkst2.prereq.FactoryAdapter;
import at.tugraz.kmi.css.cbkst2.prereq.PrerequesiteOrderSetAdapter;
import at.tugraz.kmi.css.cbkst2.prereq.PrerequesiteOrderSetGraph;

import com.google.inject.Inject;
import com.google.inject.Provider;

import edu.uci.ics.jung.io.GraphIOException;
import edu.uci.ics.jung.io.GraphMLMetadata;
import edu.uci.ics.jung.io.GraphMLWriter;
import edu.uci.ics.jung.io.graphml.EdgeMetadata;
import edu.uci.ics.jung.io.graphml.GraphMLReader2;
import edu.uci.ics.jung.io.graphml.GraphMetadata;
import edu.uci.ics.jung.io.graphml.HyperEdgeMetadata;
import edu.uci.ics.jung.io.graphml.NodeMetadata;

/**
 * A GraphML IO plugin for reading and writing {@link PrerequesiteOrderSet}'s.
 * 
 * @author Georg Öttl <georg.oettl@gmail.com>
 * 
 */
public final class GraphMLSkillStructureIO extends
		StructureLoader<CbKSTCompetence> {

	private final Provider<PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>>	skillFact;

	private final GraphMLWriter<CbKSTCompetence, PrerequesiteRelation>							gmlWriter;

	private final Provider<PrerequesiteRelation>												edgeFactory;

	private final Provider<CbKSTCompetence>														competenceFactory;

	private final Logger																		log;

	@Inject
	GraphMLSkillStructureIO(
			Provider<PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>> skill,
			Provider<CbKSTCompetence> skillFactory,
			Provider<PrerequesiteRelation> edge, Logger log) {
		super();
		skillFact = skill;
		competenceFactory = skillFactory;
		edgeFactory = edge;
		gmlWriter = new GraphMLWriter<CbKSTCompetence, PrerequesiteRelation>();
		this.log = log;

		initGmlWriter();
	}

	private void initGmlWriter() {
		gmlWriter.setEdgeIDs(new EdgeIDTransformer());
		gmlWriter.setVertexIDs(new SkillIDTransformer());
		Map<String, GraphMLMetadata<CbKSTCompetence>> vertexDataMap = new HashMap<String, GraphMLMetadata<CbKSTCompetence>>();

		// Decouple names from ID
		GraphMLMetadata<CbKSTCompetence> name = new GraphMLMetadata<CbKSTCompetence>(
				CbKSTVertex.PROPERTY_NAME, null,
				new Transformer<CbKSTCompetence, String>() {
					@Override
					public String transform(CbKSTCompetence input) {
						return input.getName();
					}
				});
		GraphMLMetadata<CbKSTCompetence> description = new GraphMLMetadata<CbKSTCompetence>(
				CbKSTVertex.PROPERTY_DESCRIPTION, null,
				new Transformer<CbKSTCompetence, String>() {
					@Override
					public String transform(CbKSTCompetence input) {
						return input.getDescription();
					}
				});

		vertexDataMap.put(CbKSTVertex.PROPERTY_NAME, name);
		vertexDataMap.put(CbKSTVertex.PROPERTY_DESCRIPTION, description);

		gmlWriter.setVertexData(vertexDataMap);
		gmlWriter
				.setVertexDescriptions(new Transformer<CbKSTCompetence, String>() {
					@Override
					public String transform(CbKSTCompetence input) {
						return input.getDescription();
					}
				});
	}

	@Override
	public PrerequesiteOrderSet<CbKSTCompetence> readStructure(Reader inputData)
			throws IOException {
		log.info("Reading GML Structure");

		MetadataGraphTransformer metadataGraphTransformer = new MetadataGraphTransformer(
				new FactoryAdapter<PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>>(
						skillFact));
		HyperEdgeMetadataTransformer hyperEdgeTransformer = new HyperEdgeMetadataTransformer(
				new FactoryAdapter<PrerequesiteRelation>(edgeFactory));
		NodeMetadataSkillTransformer vertexTransformer = new NodeMetadataSkillTransformer(
				new FactoryAdapter<CbKSTCompetence>(competenceFactory));
		EdgeMetadataTransformer edgeTransformer = new EdgeMetadataTransformer();

		GraphMLReader2<PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>, CbKSTCompetence, PrerequesiteRelation> graphMLReader = new GraphMLReader2<PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>, CbKSTCompetence, PrerequesiteRelation>(
				inputData, metadataGraphTransformer, vertexTransformer,
				edgeTransformer, hyperEdgeTransformer);
		try {
			PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation> graph = graphMLReader
					.readGraph();
			return graph;
		} catch (GraphIOException e) {
			throw new IllegalStateException(e);
		} finally {
			inputData.close();
		}
	}

	@Override
	public void writeStructure(
			ImmutablePrerequesiteOrderSet<CbKSTCompetence> structure,
			Writer outputData) throws IOException {
		log.info("Writing GML Structure");
		try {
			if (structure instanceof PrerequesiteOrderSetGraph) {
				PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation> jungPrerequesiteGraph = (PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>) structure;
				gmlWriter.save(jungPrerequesiteGraph, outputData);
			} else if (structure instanceof PrerequesiteOrderSetAdapter) {
				PrerequesiteOrderSetAdapter adapter = (PrerequesiteOrderSetAdapter) structure;
				gmlWriter.save(adapter.getJungGraph(), outputData);
			} else {
				throw new IllegalArgumentException(
						"Wrong type of structure for this loader:["
								+ structure.getClass() + "]");
			}
		} finally {
			outputData.close();
		}

	}

	@Override
	public PERSISTENCE_TYPE type() {
		return PERSISTENCE_TYPE.graphml;
	}

	// TRANSFORMERS

	private static class MetadataGraphTransformer
			implements
			Transformer<GraphMetadata, PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>> {

		private final Factory<PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>>	prereqGraph;

		MetadataGraphTransformer(
				Factory<PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation>> skill) {
			prereqGraph = skill;
		}

		@Override
		public PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation> transform(
				GraphMetadata input) {
			PrerequesiteOrderSetGraph<CbKSTCompetence, PrerequesiteRelation> newPrereqGraph = this.prereqGraph
					.create();
			Map<Object, EdgeMetadata> edgeMap = input.getEdgeMap();
			Map<Object, NodeMetadata> nodeMap = input.getNodeMap();
			HashMap<String, CbKSTCompetence> skillIDLookup = new HashMap<String, CbKSTCompetence>();
			for (Map.Entry<Object, NodeMetadata> node : nodeMap.entrySet()) {
				CbKSTCompetence s = (CbKSTCompetence) node.getKey();
				newPrereqGraph.addVertex(s);
				skillIDLookup.put(s.getId(), s);
			}

			for (Map.Entry<Object, EdgeMetadata> element : edgeMap.entrySet()) {
				String sourceName = element.getValue().getSource();
				String targetName = element.getValue().getTarget();
				String id = element.getValue().getId();
				CbKSTCompetence ancestor = skillIDLookup.get(sourceName);
				CbKSTCompetence descendant = skillIDLookup.get(targetName);
				newPrereqGraph.addEdge(
						PrerequesiteRelation.of(id, ancestor, descendant),
						ancestor, descendant);
			}
			return newPrereqGraph;
		}
	}

	private static class EdgeIDTransformer implements
			Transformer<PrerequesiteRelation, String> {

		@Override
		public String transform(PrerequesiteRelation input) {
			return input.getId();
		}

	}

	private static class HyperEdgeMetadataTransformer implements
			Transformer<HyperEdgeMetadata, PrerequesiteRelation> {

		private final Factory<PrerequesiteRelation>	edgeFact;

		HyperEdgeMetadataTransformer(Factory<PrerequesiteRelation> edge) {
			edgeFact = edge;
		}

		@Override
		public PrerequesiteRelation transform(HyperEdgeMetadata input) {
			return edgeFact.create();
		}

	}

	private static class EdgeMetadataTransformer implements
			Transformer<EdgeMetadata, PrerequesiteRelation> {

		@Override
		public PrerequesiteRelation transform(EdgeMetadata input) {
			String source = input.getSource();
			String target = input.getTarget();
			String id = input.getId();
			return PrerequesiteRelation.of(id, new CbKSTCompetence(source),
					new CbKSTCompetence(target));
		}

	}

	private static class NodeMetadataSkillTransformer implements
			Transformer<NodeMetadata, CbKSTCompetence> {

		private final Factory<CbKSTCompetence>	skillFact;

		@Inject
		NodeMetadataSkillTransformer(Factory<CbKSTCompetence> skillFactory) {
			skillFact = skillFactory;
		}

		@Override
		public CbKSTCompetence transform(NodeMetadata input) {
			CbKSTCompetence p = skillFact.create();
			String inputString = input.getId();
			p.setId(inputString);
			p.setName(input.getProperty(CbKSTVertex.PROPERTY_NAME));
			p.setDescription(input.getDescription());
			return p;
		}
	}

	private static class SkillIDTransformer implements
			Transformer<CbKSTCompetence, String> {

		@Override
		public String transform(CbKSTCompetence input) {
			return input.getId();
		}
	}

}
